package ru.nosport.matrixaria.core;

import ru.nosport.matrixaria.modules.remote.RemoteModule;
import ru.nosport.matrixaria.modules.core.rmh.RemoteMessageHandler;

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;

/**
 * User: astel1
 * Date: Oct 5, 2006
 * Time: 5:06:30 PM
 */
public class MessageHandler {
	private static String remoteHandlerName;

	/**
	 * Executes module's method with given parameters (local and remote).
	 *
	 * @param _name   Instance name
	 * @param _method Method to be called
	 * @param _params Method parameters
	 * @return Result of method execution
	 * @throws MessageHandlerException
	 */
	public static Object sendMessage(String _name, String _method, Object[] _params) throws MessageHandlerException {
		Object result = null;

		Object module = getModule(_name);
		try {
			if (module instanceof RemoteModule) {
				result = sendRemoteMessage(module, _method, _params);
			} else {
				result = sendLocalMessage(module, _method, _params);
			}
		} finally {
			releaseModule(_name, module);
		}
		return result;
	}

	/**
	 * Executes local module's method with given parameters.
	 *
	 * @param _module Module instance
	 * @param _method Method to be called
	 * @param _params Method parameters
	 * @return Result of method execution
	 * @throws MessageHandlerException
	 */
	private static Object sendLocalMessage(Object _module, String _method, Object[] _params) throws MessageHandlerException {
		Object result;
		Class[] paramClass = new Class[_params.length];

		for (int i = 0; i < _params.length; i++) {
			paramClass[i] = _params[i].getClass();
		}

		Method method;
		try {
			method = MessageHandler.getMethod(_module, _method, paramClass);
			result = method.invoke(_module, _params);
		} catch (NoSuchMethodException _ex) {
			throw new MessageHandlerException(_ex);
		} catch (IllegalAccessException _ex) {
			throw new MessageHandlerException(_ex);
		} catch (InvocationTargetException _ex) {
			throw new MessageHandlerException(_ex.getCause());
		} catch (Exception _ex) {
			throw new MessageHandlerException(_ex.getCause()/*"Unknown exception in _module " + _name + ", method " + _method + ", count params " + _params.length + "!!!  " + _ex.getMessage()*/);
		}
		return result;
	}

	/**
	 * Searches for an class's method for the object with name and parameter classes given.
	 * Parameter classes can differ from method's ones but should be assignable.
	 *
	 * @param _module reference to an object of the searchable class
	 * @param _name method name
	 * @param _params parameter classes array
	 * @return method object
	 * @throws NoSuchMethodException
	 */
	static Method getMethod(Object _module, String _name, Class[] _params) throws NoSuchMethodException {
		Method meth;
		try {
			meth = _module.getClass().getMethod(_name, _params);
		} catch(NoSuchMethodException _ex) {
			Method[] meths = _module.getClass().getMethods();

			for (Method m : meths) {
				Class[] pars = m.getParameterTypes();

				if (!m.getName().equals(_name) || (pars.length != _params.length)) {
					continue;
				}

				boolean found = true;

				for (int i = 0; i < pars.length; i++) {
					if (!pars[i].isAssignableFrom(_params[i])) {
						found = false;
						break;
					}
				}

				if (found) {
					return m;
				}
			}

			// If we are here - there's no such method. Raise an exception.
			meth = _module.getClass().getMethod(_name, _params);
		}

		return meth;
	}

	/**
	 * Executes local module's method without parameters.
	 *
	 * @param _name   Instance name
	 * @param _method Method to be called
	 * @return Result of method execution
	 * @throws MessageHandlerException
	 */
	public static Object sendMessage(String _name, String _method) throws MessageHandlerException {
		return sendMessage(_name, _method, new Object[0]);
	}

	/**
	 * Obtains shortcut to local module instance with the given name.
	 * Increases instance usage counter.
	 * <p/>
	 * releaseModule should be called to free the instance.
	 *
	 * @param _name Module instance' name
	 * @return Instance
	 * @throws MessageHandlerException
	 */
	public static Object getModule(String _name) throws MessageHandlerException {
		Object obj;

		try {
			obj = Registry.getInstance().getModule(_name);
		} catch (RegistryException _ex) {
			throw new MessageHandlerException(_ex.getMessage());
		}

		return obj;
	}

	/**
	 * Decreases instance usage.
	 *
	 * @param _name   Instance name
	 * @param _module Instance shortcut
	 * @throws MessageHandlerException
	 */
	public static void releaseModule(String _name, Object _module) throws MessageHandlerException {
		try {
			Registry.getInstance().releaseModule(_name, _module);
		} catch (RegistryException _ex) {
			throw new MessageHandlerException(_ex.getMessage());
		}
	}

	/**
	 * Calls connection to remote Core and calling remote module's method
	 * according to information in RemoteModule stub object.
	 *
	 * @param _module Stub object with connection information
	 * @param _method Method to be called
	 * @param _params Method parameters
	 * @return Result of method execution
	 * @throws MessageHandlerException
	 */
	private static Object sendRemoteMessage(Object _module, String _method, Object[] _params) throws MessageHandlerException {
		if (remoteHandlerName == null) {
			getRemoteHandlerName();
		}
		Object ret;
		RemoteModule rm = (RemoteModule) _module;
		RemoteMessageHandler rmh = (RemoteMessageHandler) getModule(remoteHandlerName);
		ret = rmh.sendMessage(rm.getAddress(), rm.getRemoteInstanceName(), _method, _params);
		releaseModule(remoteHandlerName, rmh);
		return ret;
	}

	private static void getRemoteHandlerName() throws MessageHandlerException {
		Set<String> rmhSet = Registry.getInstance().getImplementsModuleList(RemoteMessageHandler.class.getName());
		//TODO >1 and =0
		if (rmhSet.size() > 1) {
			throw new MessageHandlerException("More than one instance of "+RemoteMessageHandler.class.getName()+" registered!");
		}
		if(rmhSet.size() == 0){
			throw new MessageHandlerException("The instance of "+RemoteMessageHandler.class.getName()+" is not registered!");
		}
		String[] rmhList = new String[1];
		rmhSet.toArray(rmhList);
		remoteHandlerName = rmhList[0];
	}

}
